﻿using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;

namespace GBricks.Collections
{
  partial class Comparers
  {
    public static IEqualityComparer<IDictionary<TKey, TValue>> Dictionary<TKey, TValue>(IEqualityComparer<TKey> key = null, IEqualityComparer<TValue> value = null) {
      Contract.Ensures(Contract.Result<IEqualityComparer<IDictionary<TKey, TValue>>>() != null);
      return Dictionary<IDictionary<TKey, TValue>, TKey, TValue>(key, value);
    }

    public static IEqualityComparer<TDictionary> Dictionary<TDictionary, TKey, TValue>(IEqualityComparer<TKey> key = null, IEqualityComparer<TValue> value = null) where TDictionary : IDictionary<TKey, TValue> {
      Contract.Ensures(Contract.Result<IEqualityComparer<TDictionary>>() != null);
      return new DictionaryEqualityComparer<TDictionary, TKey, TValue>(key, value);
    }

    [Serializable]
    private sealed class DictionaryEqualityComparer<TDictionary, TKey, TValue> : EqualityComparer<TDictionary> where TDictionary : IDictionary<TKey, TValue>
    {
      public DictionaryEqualityComparer(IEqualityComparer<TKey> key = null, IEqualityComparer<TValue> value = null) {
        Key = key ?? EqualityComparer<TKey>.Default;
        Value = value ?? EqualityComparer<TValue>.Default;
      }

      private IEqualityComparer<TKey> Key { get; set; }
      private IEqualityComparer<TValue> Value { get; set; }

      [ContractInvariantMethod, ExcludeFromCodeCoverage]
      private void Invariant() {
        Contract.Invariant(Key != null);
        Contract.Invariant(Value != null);
      }

      public override bool Equals(TDictionary x, TDictionary y) {
        if(x == null) {
          return y == null;
        } else if(y == null) {
          return false;
        } else if(x.Count != y.Count) {
          return false;
        }//if

        foreach(var xitem in x) {
          TValue yvalue;
          if(!y.TryGetValue(xitem.Key, out yvalue) || !Value.Equals(xitem.Value, yvalue)) {
            return false;
          }//if
        }//for

        return true;
      }

      public override int GetHashCode(TDictionary obj) {
        return HashCode.CalculateAll(obj, item => HashCode.CalculateWith(item.Key, Key, item.Value, Value));
      }

      public override bool Equals(object obj) {
        var other = obj as DictionaryEqualityComparer<TDictionary, TKey, TValue>;
        return other != null && Equals(other.Key, Key) && Equals(other.Value, Value);
      }

      public override int GetHashCode() {
        return HashCode.Calculate(Key, Value);
      }
    }
  }
}
